package com.ltu.timkor6.gamesite.users.server;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

import com.ltu.timkor6.gamesite.server.Encrypter;
import com.ltu.timkor6.gamesite.server.GameSiteServiceImpl;
import com.ltu.timkor6.gamesite.shared.DBUsers;
import com.ltu.timkor6.gamesite.shared.UserInfo;
import com.ltu.timkor6.gamesite.users.client.RegisterStatus;
import com.ltu.timkor6.gamesite.users.client.SignInStatus;
import com.ltu.timkor6.gamesite.users.client.UsersService;

@SuppressWarnings("serial")
public class UsersServiceImpl extends GameSiteServiceImpl implements UsersService
{
	private static final int RANDOM_PASSWORD_LENGTH = 8;
	
	public UsersServiceImpl()
	{
	}
	
	// Sign in
	public SignInStatus signIn(String[] credentials)
	{		
		userInfo = dbUserInfo(credentials[0]);
		
		SignInStatus status = SignInStatus.UNKNOWN;
		
		if(userInfo == null)
		{
			status = SignInStatus.NO_USER;
		}
		else if(userInfo.getPassword() == null)
		{
			status = SignInStatus.NO_PASS;
		}
		else if(credentials[0].compareTo(userInfo.getUserName()) == 0)
		{
			String password = Encrypter.getInstance().encrypt(credentials[1]);
			if(password.compareTo(userInfo.getPassword()) == 0)
			{
				if(credentials[0].compareTo("tim") == 0)
				{
					userInfo.setIsAdmin(true);
				}
				this.setUserInSession(userInfo);
				status = SignInStatus.SUCCESS;
			}
			else
			{
				status = SignInStatus.NO_MATCH;
			}
		}
		else
		{
			status = SignInStatus.NO_MATCH;
		}
		
		return status;
	}
	
	private String randomPassword(int length)
	{
		char[] password = new char[length];
		int character = 'A';
		int random = 0;
		
		for(int i=0; i < length; i++)
		{
			// Random for number, lower case or upper case letter
			random = (int)(Math.random() * 3);
			
			switch(random)
			{
				// Is a number
				case 0:
					character = '0' + (int) (Math.random() * 10);
					break;
				// Is a lower case letter
				case 1:
					character = 'a' + (int) (Math.random() * 26);
					break;
				// Is an upper case letter
				case 2:
					character = 'A' + (int) (Math.random() * 26);
					break;
			}
			
			password[i] = (char) character;
		}
		return new String(password);
	}
	
	@SuppressWarnings("unchecked")
	public SignInStatus forgotten(String userName)
	{
		UserInfo user = dbUserInfo(userName);
		
		SignInStatus status = SignInStatus.UNKNOWN;
		
		if(user == null)
		{
			status = SignInStatus.NO_USER;
		}
		else if(user.getEmail() != null)
		{
			// TODO - Random password
			String password = "";
			
			// Update user
			PersistenceManager pm = this.getPersistenceManager();
			try
			{
				pm.currentTransaction().begin();
				
				Query query = pm.newQuery(DBUsers.class, "userName == " + userName);
				Collection<DBUsers> collection = (Collection<DBUsers>) query.execute();
				DBUsers dbUser = collection.iterator().next();
				
				if(dbUser != null)
				{
					password = randomPassword(RANDOM_PASSWORD_LENGTH);
					String encrypted = Encrypter.getInstance().encrypt(password);
					
					dbUser.setPassword(encrypted);
					
					pm.currentTransaction().commit();
				}
			}
		    catch (Throwable t)
		    {
		    	// Rollback in case of errors
		    	pm.currentTransaction().rollback();
		    }
			finally
			{
				pm.close();
			}
			
			// Send email
			if(0 < password.length())
			{
				Properties props = new Properties();
		        Session session = Session.getDefaultInstance(props, null);
		        
		        String msgBody = "Your new password is: " + password;
		        
		        try
		        {
		            MimeMessage msg = new MimeMessage(session);
		            msg.setFrom(new InternetAddress("no-reply@ltutimkor6gamesite.appspot.com", "Game Site Admin"));
		            msg.addRecipient(MimeMessage.RecipientType.TO, new InternetAddress(user.getEmail(), user.getUserName()));
		            msg.setSubject("New password request");
		            msg.setText(msgBody);
		            Transport.send(msg);
		        }
		        catch (AddressException e)
		        {
					e.printStackTrace();
		        }
		        catch (MessagingException e)
		        {
					e.printStackTrace();
		        }
		        catch (UnsupportedEncodingException e)
		        {
					e.printStackTrace();
				}
	
				status = SignInStatus.SUCCESS;
			}
		}
		
		return status;
	}
	
	@SuppressWarnings("unchecked")
	public SignInStatus profile(UserInfo user)
	{
		SignInStatus status = SignInStatus.UNKNOWN;
		
		// Update user
		PersistenceManager pm = this.getPersistenceManager();
		try
		{
			pm.currentTransaction().begin();
			
			Query query = pm.newQuery(DBUsers.class, "id == " + userInfo.getId());
			Collection<DBUsers> collection = (Collection<DBUsers>) query.execute();
			DBUsers dbUser = collection.iterator().next();
			
			if(dbUser != null)
			{
				if(0 < user.getPassword().length())
				{
					String password = Encrypter.getInstance().encrypt(user.getPassword());
					dbUser.setPassword(password);
				}
				dbUser.setEmail(user.getEmail());
				dbUser.setIsTestUser(user.isTestUser());
				
				pm.currentTransaction().commit();
				
				// Reload userInfo to dbUser
				userInfo = setUserInfo(dbUser);
				this.setUserInSession(userInfo);
				
				status = SignInStatus.SUCCESS;
			}
			else
			{
				status = SignInStatus.NO_USER;
			}
		}
	    catch (Throwable t)
	    {
	    	// Rollback in case of errors
	    	t.printStackTrace();
	    	pm.currentTransaction().rollback();
	    }
		finally
		{
			pm.close();
		}
		
		return status;
	}
	
	// Sign out
	public SignInStatus signOut()
	{
		this.invalidateSession();
		return SignInStatus.SUCCESS;
	}
	
	// Get user
	public UserInfo getUserInfo()
	{
		return this.getUserFromSession();
	}
	
	// Add user
	public RegisterStatus register(UserInfo userInfo)
	{
		String remoteIP = getThreadLocalRequest().getRemoteAddr();
		boolean catchpa = getCatchpa(remoteIP, userInfo.getCatchpaChallange(), userInfo.getCatchpaResponse());
		
		if(catchpa)
		{
			UserInfo found = dbUserInfo(userInfo.getUserName());
			if(found == null)
			{
				PersistenceManager pm = getPersistenceManager();
				try
				{
					String password = Encrypter.getInstance().encrypt(userInfo.getPassword());
					DBUsers user = new DBUsers(userInfo.getUserName(), password);
					user.setIsTestUser(userInfo.isTestUser());
					user.setEmail(userInfo.getEmail());
					
					pm.makePersistent(user);
				}
				finally
				{
					pm.close();
				}
				
				return RegisterStatus.SUCCESS;
			}
			else
			{
				return RegisterStatus.USERNAME_TAKEN;
			}
		}
		else
		{
			return RegisterStatus.CATCHPA_ERROR;
		}
	}
	
	private boolean getCatchpa(String remoteIP, String challange, String response)
	{
		String privateKey = "6Lc8W8ASAAAAAJB3_Z1I2mF3UktSi5pquGfMuMXZ ";
		boolean success = false;
		
		try
		{
			// Construct data
			
			// Private key
			String data = URLEncoder.encode("privatekey", "UTF-8") + "=" + URLEncoder.encode(privateKey, "UTF-8");

			// Users remote ip
			data += "&" + URLEncoder.encode("remoteip", "UTF-8") + "=" + URLEncoder.encode(remoteIP, "UTF-8");

			// Catchpa challenge
			data += "&" + URLEncoder.encode("challenge", "UTF-8") + "=" + URLEncoder.encode(challange, "UTF-8");

			// Catchpa response
			data += "&" + URLEncoder.encode("response", "UTF-8") + "=" + URLEncoder.encode(response, "UTF-8");
			
			// Send data
			URL url = new URL("http://www.google.com/recaptcha/api/verify");
			URLConnection conn = url.openConnection();
			conn.setDoOutput(true);
			OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
			wr.write(data);
			wr.flush();
			
			// Get the response
			BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			String line;
			int index = 0;
			while ((line = rd.readLine()) != null)
			{
				if(index == 0)
				{
					success = Boolean.parseBoolean(line);
				}
				index++;
			}
			wr.close();
			rd.close();
		} catch (Exception e)
		{
		}
		
		return success;
	}
	
	// Fetch user from database
	@SuppressWarnings("unchecked")
	private UserInfo dbUserInfo(String username)
	{
		PersistenceManager pm = getPersistenceManager();
		
		List<UserInfo> list = new ArrayList<UserInfo>();
		try
		{
			Query q = pm.newQuery(DBUsers.class, "userName == u");
			q.declareParameters("String u");
			List<DBUsers> db = (List<DBUsers>) q.execute(username);
			for(DBUsers info : db)
			{
				UserInfo user = setUserInfo(info);
				
				list.add(user);
			}
		}
		finally
		{
			pm.close();
		}
		
		if(list.isEmpty())
		{
			return null;
		}
		else
		{
			return list.get(0);
		}
	}
}
